/*
 * (c) 2012 Leonid Bensman, lbensman.cs00@gtalumni.org
 */
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Security.Cryptography;

namespace CalcSig
{
    public class ImagePHash : ItemHash
    {
        public PHash PerceptualHash0;
        public PHash PerceptualHash90;
        public PHash PerceptualHash180;
        public PHash PerceptualHash270;
        public byte[] FileHash;
    }

    class ImagePHashAverageCalculator : FileContentHashCalculator<ImagePHash>
    {
        public static readonly string[] SupportedFileSearchMasks = { "*.JPG", "*.JPEG", "*.BMP", "*.GIF" };

        public ImagePHashAverageCalculator(DirectoryEnumerator fileEnumerator)
            : base(fileEnumerator.Enumerate())
        {
        }

        protected override void OnCalculateFileHash(ImagePHash itemHash)
        {
            try
            {
                PerceptualHashAlgorithm pHashProvider = PerceptualHashAlgorithm.Create(16, 8);

                using (Stream fileStream = File.OpenRead(itemHash.Path))
                {
                    using (Bitmap theImage = new Bitmap(fileStream))
                    {
                        itemHash.PerceptualHash0 = pHashProvider.AverageHash(theImage);
                        theImage.RotateFlip(RotateFlipType.Rotate90FlipNone);
                        itemHash.PerceptualHash90 = pHashProvider.AverageHash(theImage);
                        theImage.RotateFlip(RotateFlipType.Rotate90FlipNone);
                        itemHash.PerceptualHash180 = pHashProvider.AverageHash(theImage);
                        theImage.RotateFlip(RotateFlipType.Rotate90FlipNone);
                        itemHash.PerceptualHash270 = pHashProvider.AverageHash(theImage);
                    }
                }
            }
            catch (Exception e)
            {
                itemHash.PerceptualHash0 = null;
                Console.WriteLine(string.Format("Exception while processing {0} for image hash.  Exception: {1}", itemHash.Path, e.ToString()));
            }

            try
            {
                MD5 md5 = MD5.Create();

                using (Stream fs = new FileStream(itemHash.Path, FileMode.Open, FileAccess.Read, FileShare.Read))
                {
                    itemHash.FileHash = md5.ComputeHash(fs);
                }
            }
            catch (Exception e)
            {
                itemHash.FileHash = null;
                Console.WriteLine(string.Format("Exception while processing {0} for file hash.  Exception: {1}", itemHash.Path, e.ToString()));
            }


            base.OnCalculateFileHash(itemHash);
        }

        public static ulong AverageHash(System.Drawing.Image theImage)
        {
            // Squeeze the image down to an 8x8 6-bit grayscale image.

            // Chant the ancient incantations to create the correct data structures.
            Bitmap squeezedImage = new Bitmap(8, 8, PixelFormat.Format32bppRgb);
            Graphics drawingArea = Graphics.FromImage(squeezedImage);
            drawingArea.CompositingQuality = CompositingQuality.HighQuality;
            drawingArea.InterpolationMode = InterpolationMode.HighQualityBilinear;
            drawingArea.SmoothingMode = SmoothingMode.HighQuality;
            drawingArea.DrawImage(theImage, 0, 0, 8, 8);
            byte[] grayScaleImage = new byte[64];

            uint averageValue = 0;
            ulong finalHash = 0;

            // Reduce the colour to 6-bit grayscale and calculate the average value.
            for (int y = 0; y < 8; y++)
            {
                for (int x = 0; x < 8; x++)
                {
                    // Add together the red, green and blue
                    // components and reduce them to 6 bit grayscale.
                    uint pixelColour = (uint)squeezedImage.GetPixel(x, y).ToArgb();
                    uint grayTone = (pixelColour & 0x00FF0000) >> 16;
                    grayTone += (pixelColour & 0x0000FF00) >> 8;
                    grayTone += (pixelColour & 0x000000FF);
                    grayTone /= 12;

                    grayScaleImage[x + y * 8] = (byte)grayTone;
                    averageValue += grayTone;
                }
            }
            averageValue /= 64;

            // Return 1-bits when the tone is equal to or above the average,
            // and 0-bits when it's below the average.
            for (int k = 0; k < 64; k++)
            {
                //if (k % 8 == 0)
                //    Console.WriteLine();

                if (grayScaleImage[k] >= averageValue)
                {
                    finalHash |= (1UL << (63 - k));
                    //Console.Write(" ");
                }
                //else
                    //Console.Write("#");
            }
            //Console.WriteLine();
            //Console.WriteLine();

            return finalHash;
        }
    }
}
